package com.socio.threads;

import java.util.Map;

import org.json.JSONObject;

import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import com.google.gson.Gson;
import com.socio.activities.R;
import com.socio.activities.SocioSettings;
import com.socio.model.ClientUser;
import com.socio.model.responses.DayLeaderboardAlltimeResponse;
import com.socio.model.responses.DayLeaderboardResponse;
import com.socio.model.responses.FacebookUserUpdateResponse;
import com.socio.model.responses.SettingsGetResponse;
import com.socio.model.responses.SettingsUpdateResponse;
import com.socio.model.responses.TwitterUserUpdateResponse;
import com.socio.utils.SocioUtils;

public class BackendThread extends Thread {

	private static final Gson gson = new Gson();
	
	public static final int RECEIVE_HANDLER = 0;
	public static final int GET_LEADERBOARD = 1;
	public static final int GET_SETTINGS = 2;
	public static final int UPDATE_SETTINGS = 3;
	public static final int QUIT = 4;
	public static final int UPDATE_FACEBOOKID = 5;
	public static final int UPDATE_TWITTERACCESSTOKEN = 6;
	public static final int UPDATE_FOURSQUAREACCESSTOKEN = 7;
	
	public static final int TROUBLE = 0;
	public static final int SUCCESS = 1;
	public static final int NO_RESULTS = 2;
	public static final int TRY_AGAIN = 3;
	public static final int CANCELED = 4;
	
	public static final int DAY = 0;
	public static final int ALL_TIME = 1;
	
	private final Uri.Builder uriBuilder = new Uri.Builder();
	
	private Handler handler;
	private String socioHost;
	private Context context;
	private Handler ownersHandler;
	private Message currentMessage;
	
	/**
	 * Mode in which the thread has been canceled temporarily
	 * When cancel previous run is called 
	 * 1) Puts thread in cancel mode that prevents any other message from canceled run from being sent
	 * 2) Puts thread in mode that stops it from executing any network intensive action
	 * A subsequent call to the run will return thread to normal operations
	 */
	private int canceledMode = -1;

	public BackendThread(Context context, Handler ownersHandler) {
		this.context = context;
		this.ownersHandler = ownersHandler;
	}
	
	/**
	 * Set mode to be in canceled state. Send cancel confirmation message
	 * 
	 * @param mode Mode to be canceled
	 */
	public void cancelMessageForMode(int mode) {
		canceledMode = mode;
		handler.removeMessages(mode);
	}
	
	/**
	 * Get all time leaderboard
	 * 
	 * @param extraParams Extra params needed for request
	 * @param mode Mode that thread is operating in
	 */
	private void getAllTimeLeaderboard(Map<String, Object> extraParams, int mode) {
		
		String udid = (String)extraParams.get("udid");
		DayLeaderboardAlltimeResponse clientResponse = null;
		Message msg = ownersHandler.obtainMessage(mode);
		
		// construct url
		uriBuilder.encodedQuery("");
		uriBuilder.encodedPath(socioHost +"resources/day/leaderboard/alltime");
		uriBuilder.appendQueryParameter("udid", udid);
		
		// networking
		try {
			// run if it hasn't been canceled
			if(mode != canceledMode)
				clientResponse = (DayLeaderboardAlltimeResponse)SocioUtils.getResponseFromURLRequest(uriBuilder, DayLeaderboardAlltimeResponse.class);
			else
				return;
		} catch (Exception e) {}
		
		// if exception, clientResponse is not received, or not successful
		if(clientResponse == null || !clientResponse.isSuccess()) {
			msg.arg1 = TROUBLE;
			msg.sendToTarget();
			return;
		}
		
		// if no results are found
		if(clientResponse.getUsers().size() == 0) {
			msg.arg1 = NO_RESULTS;
			msg.sendToTarget();
			return;
		}
		
		// otherwise its a success
		msg.arg1 = SUCCESS;
		msg.obj = clientResponse.getUsers();
		msg.arg2 = ALL_TIME;
		msg.sendToTarget();
	}
	
	/**
	 * Get day's leaderboard
	 * 
	 * @param extraParams Extra params needed for request
	 * @param mode Mode that thread is operating in
	 */
	private void getDayLeaderboard(Map<String, Object> extraParams, int mode) {

		String udid = (String)extraParams.get("udid");
		String dayTime = (String)extraParams.get("dayTime");
		DayLeaderboardResponse clientResponse = null;
		Message msg = ownersHandler.obtainMessage(mode);
		
		// construct url
		uriBuilder.encodedQuery("");
		uriBuilder.encodedPath(socioHost +"resources/day/leaderboard");
		uriBuilder.appendQueryParameter("udid", udid);
		uriBuilder.appendQueryParameter("dayTime", dayTime);
		
		// networking
		try {
			// run if it hasn't been canceled
			if(mode != canceledMode)
				clientResponse = (DayLeaderboardResponse)SocioUtils.getResponseFromURLRequest(uriBuilder, DayLeaderboardResponse.class);
			else
				return;
		} catch (Exception e) {}
		
		// if exception, clientResponse is not received, or not successful
		if(clientResponse == null || !clientResponse.isSuccess()) {
			msg.arg1 = TROUBLE;
			msg.sendToTarget();
			return;
		}
		
		// if no results are found
		if(clientResponse.getUsers().size() == 0) {
			msg.arg1 = NO_RESULTS;
			msg.sendToTarget();
			return;
		}
		
		// otherwise its a success
		msg.arg1 = SUCCESS;
		msg.obj = clientResponse.getUsers();
		msg.arg2 = DAY;
		msg.sendToTarget();
	}
	
	/**
	 * Gets a leaderboard
	 * 
	 * @param extraParams Extra params needed for request
	 * @param mode Mode that thread is operating in
	 */
	private void getLeaderboard(Map<String, Object> extraParams, int mode) {
		int getLeaderboardType = (Integer)extraParams.get("getLeaderboardType");
		switch(getLeaderboardType) {
		case DAY:
			getDayLeaderboard(extraParams, mode);
			break;
		case ALL_TIME:
			getAllTimeLeaderboard(extraParams, mode);
			break;
		}
	}
	
	/**
	 * Gets user's settings
	 * 
	 * @param extraParams Extra params needed for request
	 * @param mode Mode that thread is operating in
	 */
	private void getSettings(Map<String, Object> extraParams, int mode) {
		String udid = (String)extraParams.get("udid");
		SettingsGetResponse clientResponse = null;
		Message msg = ownersHandler.obtainMessage(mode);
		
		// construct url
		uriBuilder.encodedQuery("");
		uriBuilder.encodedPath(socioHost +"resources/settings/get");
		uriBuilder.appendQueryParameter("udid", udid);
		
		// networking
		try {
			// run if it hasn't been canceled
			if(mode != canceledMode)
				clientResponse = (SettingsGetResponse)SocioUtils.getResponseFromURLRequest(uriBuilder, SettingsGetResponse.class);
			else
				return;
		} catch (Exception e) {}
		
		// if exception, clientResponse is not received, or not successful
		if(clientResponse == null || !clientResponse.isSuccess()) {
			msg.arg1 = TROUBLE;
			msg.sendToTarget();
			return;
		}
		
		// if no results are found
		if(clientResponse.getUser() == null) {
			msg.arg1 = NO_RESULTS;
			msg.sendToTarget();
			return;
		}
		
		// otherwise its a success
		msg.arg1 = SUCCESS;
		msg.obj = clientResponse.getUser();
		msg.sendToTarget();
	}
	
	@SuppressWarnings("unchecked")
	private void handleOwnersMessage(Message msg) {
		
		currentMessage = msg;
		
		// un cancel a mode on subsequent calls
		if(canceledMode == msg.what)
			canceledMode = -1;
		
		switch (msg.what) {
		case GET_LEADERBOARD:
			getLeaderboard((Map<String, Object>)msg.obj, msg.what);
			break;
		case GET_SETTINGS:
			getSettings((Map<String, Object>)msg.obj, msg.what);
			break;
		case QUIT:
			quit();
			break;
		case UPDATE_FACEBOOKID:
			updateFacebookID((Map<String, Object>)msg.obj, msg.what);
			break;
		case UPDATE_FOURSQUAREACCESSTOKEN:
			updateFoursquareAccessToken((Map<String, Object>)msg.obj, msg.what);
			break;
		case UPDATE_SETTINGS:
			updateSettings((Map<String, Object>)msg.obj, msg.what);
			break;
		case UPDATE_TWITTERACCESSTOKEN:
			updateTwitterAccessToken((Map<String, Object>)msg.obj, msg.what);
			break;
		default:
			break;
		}
	}
	
	/**
	 * Pauses a message for later execution
	 * 
	 * @return message
	 */
	public Message pauseCurrentMessage() {
		if(currentMessage != null) {
			cancelMessageForMode(currentMessage.what);
			Message resumeMessage = Message.obtain();
			resumeMessage.what = currentMessage.what;
			resumeMessage.obj = currentMessage.obj;
			return resumeMessage;
		}
		return null;
	}

	/**
	 * Stop looper for this thread
	 */
	private void quit() {
		Looper looper = Looper.myLooper();
		if(looper != null)
			looper.quit();
	}
	
	@Override
	public void run() {

		Looper.prepare();
        
		socioHost = context.getResources().getString(R.string.socio_host);
		
		handler = new Handler() { 
			public void handleMessage(Message msg) { 
				handleOwnersMessage(msg);
			}
		};
		
		Message msg = ownersHandler.obtainMessage(RECEIVE_HANDLER);
        msg.obj = handler;
        msg.sendToTarget();
        
        Looper.loop();

	}
	
	/**
	 * Updates user's facebook id
	 * 
	 * @param extraParams Extra params needed for request
	 * @param mode Mode that thread is operating in
	 */
	private void updateFacebookID(Map<String, Object> extraParams, int mode) {
		
		ClientUser clientUser = (ClientUser)extraParams.get("clientUser");
		String accessToken = (String)extraParams.get("accessToken");
		FacebookUserUpdateResponse clientResponse = null;
		Message msg = ownersHandler.obtainMessage(mode);
		
		// construct url
		uriBuilder.encodedQuery("");
		uriBuilder.encodedPath("https://graph.facebook.com/me").appendQueryParameter("access_token", accessToken);
		
		// networking
		String facebookID = "";
		try {
			// run if it hasn't been canceled
			if(mode != canceledMode) {
				String response = SocioUtils.getStringFromURLRequest(uriBuilder);
				JSONObject jsonObject = new JSONObject(response);
				facebookID = jsonObject.getString("id");
			} else {
				return;
			}
		} catch (Exception e) {
			msg.arg1 = TROUBLE;
			msg.sendToTarget();
			return;
		}
		
		clientUser.setFacebookID(facebookID);
		String json = gson.toJson(clientUser);
		
		// construct url
		uriBuilder.encodedQuery("");
		uriBuilder.encodedPath(socioHost +"resources/facebook/user/update");
		
		// networking
		try {
			// run if it hasn't been canceled
			if(mode != canceledMode)
				clientResponse = (FacebookUserUpdateResponse)SocioUtils.postStringToURL(uriBuilder, json, FacebookUserUpdateResponse.class);
			else
				return;
		} catch (Exception e) {}
		
		// if exception, clientResponse is not set, or is unsuccessful
		if(clientResponse == null || !clientResponse.isSuccess()) {
			msg.arg1 = TROUBLE;
			msg.sendToTarget();
			return;
		}
		
		// otherwise it was success
		msg.arg1 = SUCCESS;
		
		// if run not canceled, send message
		if(mode != canceledMode)
			msg.sendToTarget();
	}
	
	/**
	 * Updates user's foursquare access token
	 * 
	 * @param extraParams Extra params needed for request
	 * @param mode Mode that thread is operating in
	 */
	private void updateFoursquareAccessToken(Map<String, Object> extraParams, int mode) {
		
		ClientUser clientUser = (ClientUser)extraParams.get("clientUser");
		String code = (String)extraParams.get("code");
		FoursquareUserUpdateResponse clientResponse = null;
		Message msg = ownersHandler.obtainMessage(mode);
		String foursquareAccessToken = "";
		
		// construct url if code not empty
		if(code.length() > 0) {
			uriBuilder.encodedQuery("");
			uriBuilder.encodedPath("https://foursquare.com/oauth2/access_token")
			.appendQueryParameter("client_id", SocioSettings.FOURSQUARE_CLIENT_ID)
			.appendQueryParameter("client_secret", SocioSettings.FOURSQUARE_CLIENT_SECRET)
			.appendQueryParameter("grant_type", "authorization_code")
			.appendQueryParameter("redirect_uri", SocioSettings.FOURSQUARE_CALLBACK_URL)
			.appendQueryParameter("code", code);
			
			// get access token
			try {
				// run if it hasn't been canceled
				if(mode != canceledMode) {
					String response = SocioUtils.getStringFromURLRequest(uriBuilder);
					JSONObject jsonObject = new JSONObject(response);
					foursquareAccessToken = jsonObject.getString("access_token");
				} else {
					return;
				}
			} catch (Exception e) {
				msg.arg1 = TROUBLE;
				msg.sendToTarget();
				return;
			}
		} else {
			
		}
		
		clientUser.setFoursquareAccessToken(foursquareAccessToken);
		String json = gson.toJson(clientUser);
		
		// construct url
		uriBuilder.encodedQuery("");
		uriBuilder.encodedPath(socioHost +"resources/foursquare/user/update");
		
		// networking
		try {
			// run if it hasn't been canceled
			if(mode != canceledMode)
				clientResponse = (FoursquareUserUpdateResponse)SocioUtils.postStringToURL(uriBuilder, json, FoursquareUserUpdateResponse.class);
			else
				return;
		} catch (Exception e) {}
		
		// if exception, clientResponse is not set, or is unsuccessful
		if(clientResponse == null || !clientResponse.isSuccess()) {
			msg.arg1 = TROUBLE;
			msg.sendToTarget();
			return;
		}
		
		// otherwise it was success
		msg.arg1 = SUCCESS;
		
		// if run not canceled, send message
		if(mode != canceledMode)
			msg.sendToTarget();
	}
	
	/**
	 * Updates user's settings
	 * 
	 * @param extraParams Extra params needed for request
	 * @param mode Mode that thread is operating in
	 */
	private void updateSettings(Map<String, Object> extraParams, int mode) {
		
		ClientUser clientUser = (ClientUser)extraParams.get("clientUser");
		SettingsUpdateResponse clientResponse = null;
		Message msg = ownersHandler.obtainMessage(mode);
		String json = gson.toJson(clientUser);
		
		// construct url
		uriBuilder.encodedQuery("");
		uriBuilder.encodedPath(socioHost +"resources/settings/update");
		
		// networking
		try {
			// run if it hasn't been canceled
			if(mode != canceledMode)
				clientResponse = (SettingsUpdateResponse)SocioUtils.postStringToURL(uriBuilder, json, SettingsUpdateResponse.class);
			else
				return;
		} catch (Exception e) {}
		
		// if exception, clientResponse is not set, or is unsuccessful
		if(clientResponse == null || !clientResponse.isSuccess()) {
			msg.arg1 = TROUBLE;
			msg.sendToTarget();
			return;
		}
		
		// otherwise it was success
		msg.arg1 = SUCCESS;
		
		// if run not canceled, send message
		if(mode != canceledMode)
			msg.sendToTarget();
	}
	
	/**
	 * Updates user's twitter access token
	 * 
	 * @param extraParams Extra params needed for request
	 * @param mode Mode that thread is operating in
	 */
	private void updateTwitterAccessToken(Map<String, Object> extraParams, int mode) {
		
		ClientUser clientUser = (ClientUser)extraParams.get("clientUser");
		TwitterUserUpdateResponse clientResponse = null;
		Message msg = ownersHandler.obtainMessage(mode);
		String json = gson.toJson(clientUser);
		
		// construct url
		uriBuilder.encodedQuery("");
		uriBuilder.encodedPath(socioHost +"resources/twitter/user/update");
		
		// networking
		try {
			// run if it hasn't been canceled
			if(mode != canceledMode)
				clientResponse = (TwitterUserUpdateResponse)SocioUtils.postStringToURL(uriBuilder, json, TwitterUserUpdateResponse.class);
			else
				return;
		} catch (Exception e) {}
		
		// if exception, clientResponse is not set, or is unsuccessful
		if(clientResponse == null || !clientResponse.isSuccess()) {
			msg.arg1 = TROUBLE;
			msg.sendToTarget();
			return;
		}
		
		// otherwise it was success
		msg.arg1 = SUCCESS;
		
		// if run not canceled, send message
		if(mode != canceledMode)
			msg.sendToTarget();
	}
}
